package br.ufpe.cin.mac.nameService;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

import br.ufpe.cin.mac.demo.StringServer.IStringServer;
import br.ufpe.cin.mac.demo.StringServer.StringClient;
import br.ufpe.cin.mac.demo.StringServer.StringServer;
import br.ufpe.cin.mac.middleware.Middleware;
import br.ufpe.cin.mac.util.Marshal;
import br.ufpe.cin.mac.util.Proxy;

public class NameServiceClient {

	private SocketChannel channel;
	private String server;

	private static NameServiceClient instance;

	public NameServiceClient(String server) {
		this.server = server;
		try {
			channel = SocketChannel.open();
			channel.connect(new InetSocketAddress(server, 2099));

		} catch (UnknownHostException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	

	}
	
	private void connect(){
		if(channel.isConnected()){
			return;
		}
		/*try {
			channel = SocketChannel.open();
			channel.connect(new InetSocketAddress(server, 2099));

		} catch (UnknownHostException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}*/
	}
	
	private void disconnect(){
		if(!channel.isConnected()){
			return;
		}
		/*try {
			channel.close();
			
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}*/
	}


	public boolean bind(Object iOjbect, String port) {
		String host;		
		try {
			host = InetAddress.getLocalHost().getHostAddress();
			//String op = "bind:" + host + ":" + port;
			String op = "bind:" + port;
			
			connect();
			//Enviando OP
			int writeBytes = sendString(channel, op);
			
			if(writeBytes == 0){
				System.err.println("Erro BIND ");
				disconnect();
				return false;
			}
			
			ByteBuffer buffer = ByteBuffer.allocate(1);
			channel.read(buffer);//must be 1
			
			//Enviando OBJ
			writeBytes = sendObject(channel, iOjbect);
			
			if(writeBytes == 0){
				System.err.println("Erro BIND ");
				disconnect();
				return false;
			}
			disconnect();
			return true;
		} catch (Exception e) {
			e.printStackTrace();
			disconnect();
			return false;
		}
	}

	public Object lookup(String service) {
		String op = "lookup:" + service;
		try {
			connect();
			//Enviando OP
			int writeBytes = sendString(channel, op);
			
			if(writeBytes == 0){
				System.err.println("Erro BIND ");
				return false;
			}
			
			ByteBuffer buffer = ByteBuffer.allocate(5000);
			buffer.clear();
			channel.read(buffer);//Must be 1
			
			//Send Confirmation
			buffer.clear();
			buffer.put((byte)1);
			buffer.flip();
			channel.write(buffer);
			
			buffer.clear();
			channel.read(buffer);
			buffer.flip();
			
			//Recover Server String
			byte[] serialized = new byte[buffer.limit()];
			buffer.get(serialized);
			String server = Marshal.recoverString(serialized);
			
			//Send Confirmation
			buffer.clear();
			buffer.put((byte)1);
			buffer.flip();
			channel.write(buffer);
			
			//GetOBJ
			buffer.clear();
			channel.read(buffer);
			buffer.flip();
			disconnect();
			Class<?> iObject = (Class<?>)Marshal.recoverObject(buffer.array());
			
			Object proxy = createProxy(iObject, server);
			disconnect();
			return proxy;
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

	private Object createProxy(Class<?> iObject, String server) {

		Middleware mid = new Middleware(server, "Config.xml");

		Proxy m = new Proxy(new StringClient(), StringServer.class, mid);

		IStringServer proxy = (IStringServer) java.lang.reflect.Proxy
				.newProxyInstance(Thread.currentThread()
						.getContextClassLoader(), new Class[] { iObject },
						(Proxy) m);

		return proxy;
	}
	
	private int sendString(SocketChannel channel, String str) {
		int ret = 0;
		try {
			byte[] serialized = Marshal.serializeString(str);
			ByteBuffer buffer = ByteBuffer.wrap(serialized);			
			ret = channel.write(buffer);			
		
		} catch (IOException e) {
			e.printStackTrace();
		}
		return ret;
	
	}

	private int sendObject(SocketChannel channel, Object str) {
		int ret = 0;
		try {
			byte[] serialized = Marshal.serializeObject(str);
			ByteBuffer buffer = ByteBuffer.wrap(serialized) ;			
			return channel.write(buffer);			
		
		} catch (IOException e) {
			e.printStackTrace();
		}
		return ret;
	}

	public static void main(String[] args) {

		NameServiceClient nsc = new NameServiceClient("127.0.0.1");
		System.out.println(nsc.bind(IStringServer.class, "48484"));
		System.out.println(nsc.lookup("Teste"));
	}
}
